<template>
	<transition name="el-zoom-in-top" @after-leave="$emit('dodestroy')">
		<div
			v-show="visible"
			class="el-picker-panel el-date-range-picker el-popper"
			:class="[
				{
					'has-sidebar': $slots.sidebar || shortcuts,
					'has-time': showTime
				},
				popperClass
			]">
			<div class="el-picker-panel__body-wrapper">
				<slot name="sidebar" class="el-picker-panel__sidebar"></slot>
				<div class="el-picker-panel__sidebar" v-if="shortcuts">
					<button
						type="button"
						class="el-picker-panel__shortcut"
						v-for="(shortcut, key) in shortcuts"
						:key="key"
						@click="handleShortcutClick(shortcut)">
						{{ shortcut.text }}
					</button>
				</div>
				<div class="el-picker-panel__body">
					<div class="el-date-range-picker__time-header" v-if="showTime">
						<span class="el-date-range-picker__editors-wrap">
							<span class="el-date-range-picker__time-picker-wrap">
								<xInput
									size="small"
									:disabled="rangeState.selecting"
									ref="minInput"
									:placeholder="i18n('el.datepicker.startDate')"
									class="el-date-range-picker__editor"
									:value="minVisibleDate"
									@input="val => handleDateInput(val, 'min')"
									@change="val => handleDateChange(val, 'min')" />
							</span>
							<span
								class="el-date-range-picker__time-picker-wrap"
								v-clickoutside="handleMinTimeClose">
								<xInput
									size="small"
									class="el-date-range-picker__editor"
									:disabled="rangeState.selecting"
									:placeholder="i18n('el.datepicker.startTime')"
									:value="minVisibleTime"
									@focus="minTimePickerVisible = true"
									@input="val => handleTimeInput(val, 'min')"
									@change="val => handleTimeChange(val, 'min')" />
								<PanelTime
									ref="minTimePicker"
									@pick="handleMinTimePick"
									:time-arrow-control="arrowControl"
									:visible="minTimePickerVisible"
									@mounted="$refs.minTimePicker.format = timeFormat">
								</PanelTime>
							</span>
						</span>
						<span class="el-icon-arrow-right"></span>
						<span class="el-date-range-picker__editors-wrap is-right">
							<span class="el-date-range-picker__time-picker-wrap">
								<xInput
									size="small"
									class="el-date-range-picker__editor"
									:disabled="rangeState.selecting"
									:placeholder="i18n('el.datepicker.endDate')"
									:value="maxVisibleDate"
									:readonly="!minDate"
									@input="val => handleDateInput(val, 'max')"
									@change="val => handleDateChange(val, 'max')" />
							</span>
							<span
								class="el-date-range-picker__time-picker-wrap"
								v-clickoutside="handleMaxTimeClose">
								<xInput
									size="small"
									class="el-date-range-picker__editor"
									:disabled="rangeState.selecting"
									:placeholder="i18n('el.datepicker.endTime')"
									:value="maxVisibleTime"
									:readonly="!minDate"
									@focus="minDate && (maxTimePickerVisible = true)"
									@input="val => handleTimeInput(val, 'max')"
									@change="val => handleTimeChange(val, 'max')" />
								<PanelTime
									ref="maxTimePicker"
									@pick="handleMaxTimePick"
									:time-arrow-control="arrowControl"
									:visible="maxTimePickerVisible"
									@mounted="$refs.maxTimePicker.format = timeFormat">
								</PanelTime>
							</span>
						</span>
					</div>
					<div class="el-picker-panel__content el-date-range-picker__content is-left">
						<div class="el-date-range-picker__header">
							<button
								type="button"
								@click="leftPrevYear"
								class="el-picker-panel__icon-btn el-icon-d-arrow-left"></button>
							<button
								type="button"
								@click="leftPrevMonth"
								class="el-picker-panel__icon-btn el-icon-arrow-left"></button>
							<button
								type="button"
								@click="leftNextYear"
								v-if="unlinkPanels"
								:disabled="!enableYearArrow"
								:class="{ 'is-disabled': !enableYearArrow }"
								class="el-picker-panel__icon-btn el-icon-d-arrow-right"></button>
							<button
								type="button"
								@click="leftNextMonth"
								v-if="unlinkPanels"
								:disabled="!enableMonthArrow"
								:class="{ 'is-disabled': !enableMonthArrow }"
								class="el-picker-panel__icon-btn el-icon-arrow-right"></button>
							<div>{{ leftLabel }}</div>
						</div>
						<BasicDateTable
							selection-mode="range"
							:date="leftDate"
							:default-value="defaultValue"
							:min-date="minDate"
							:max-date="maxDate"
							:range-state="rangeState"
							:disabled-date="disabledDate"
							:cell-class-name="cellClassName"
							@changerange="handleChangeRange"
							:first-day-of-week="firstDayOfWeek"
							@pick="handleRangePick">
						</BasicDateTable>
					</div>
					<div class="el-picker-panel__content el-date-range-picker__content is-right">
						<div class="el-date-range-picker__header">
							<button
								type="button"
								@click="rightPrevYear"
								v-if="unlinkPanels"
								:disabled="!enableYearArrow"
								:class="{ 'is-disabled': !enableYearArrow }"
								class="el-picker-panel__icon-btn el-icon-d-arrow-left"></button>
							<button
								type="button"
								@click="rightPrevMonth"
								v-if="unlinkPanels"
								:disabled="!enableMonthArrow"
								:class="{ 'is-disabled': !enableMonthArrow }"
								class="el-picker-panel__icon-btn el-icon-arrow-left"></button>
							<button
								type="button"
								@click="rightNextYear"
								class="el-picker-panel__icon-btn el-icon-d-arrow-right"></button>
							<button
								type="button"
								@click="rightNextMonth"
								class="el-picker-panel__icon-btn el-icon-arrow-right"></button>
							<div>{{ rightLabel }}</div>
						</div>
						<BasicDateTable
							selection-mode="range"
							:date="rightDate"
							:default-value="defaultValue"
							:min-date="minDate"
							:max-date="maxDate"
							:range-state="rangeState"
							:disabled-date="disabledDate"
							:cell-class-name="cellClassName"
							@changerange="handleChangeRange"
							:first-day-of-week="firstDayOfWeek"
							@pick="handleRangePick">
						</BasicDateTable>
					</div>
				</div>
			</div>
			<div class="el-picker-panel__footer" v-if="showTime">
				<xBtn
					size="mini"
					preset="text"
					class="el-picker-panel__link-btn"
					@click="handleClear">
					{{ i18n("el.datepicker.clear") }}
				</xBtn>
				<xBtn
					plain
					size="mini"
					class="el-picker-panel__link-btn"
					:disabled="btnDisabled"
					@click="handleConfirm(false)">
					{{ i18n("el.datepicker.confirm") }}
				</xBtn>
			</div>
		</div>
	</transition>
</template>
<script lang="ts">
export default async function () {
	const [Clickoutside, dateUtils] = await _.$importVue([
		"/common/ui-x/directive/clickoutside.vue",
		"/common/ui-x/components/form/xDatePicker/dateUtils.vue"
	]);
	const {
		formatDate,
		parseDate,
		isDate,
		modifyDate,
		modifyTime,
		modifyWithTimeString,
		prevYear,
		nextYear,
		prevMonth,
		nextMonth,
		nextDate,
		extractDateFormat,
		extractTimeFormat
	} = dateUtils;

	const calcDefaultValue = defaultValue => {
		if (Array.isArray(defaultValue)) {
			return [new Date(defaultValue[0]), new Date(defaultValue[1])];
		} else if (defaultValue) {
			return [new Date(defaultValue), nextDate(new Date(defaultValue), 1)];
		} else {
			return [new Date(), nextDate(new Date(), 1)];
		}
	};

	return defineComponent({
		directives: { Clickoutside },
		computed: {
			btnDisabled() {
				return !(
					this.minDate &&
					this.maxDate &&
					!this.selecting &&
					this.isValidValue([this.minDate, this.maxDate])
				);
			},

			leftLabel() {
				return (
					this.leftDate.getFullYear() +
					" " +
					this.i18n("el.datepicker.year") +
					" " +
					this.i18n(`el.datepicker.month${this.leftDate.getMonth() + 1}`)
				);
			},

			rightLabel() {
				return (
					this.rightDate.getFullYear() +
					" " +
					this.i18n("el.datepicker.year") +
					" " +
					this.i18n(`el.datepicker.month${this.rightDate.getMonth() + 1}`)
				);
			},

			leftYear() {
				return this.leftDate.getFullYear();
			},

			leftMonth() {
				return this.leftDate.getMonth();
			},

			leftMonthDate() {
				return this.leftDate.getDate();
			},

			rightYear() {
				return this.rightDate.getFullYear();
			},

			rightMonth() {
				return this.rightDate.getMonth();
			},

			rightMonthDate() {
				return this.rightDate.getDate();
			},

			minVisibleDate() {
				if (this.dateUserInput.min !== null) return this.dateUserInput.min;
				if (this.minDate) return formatDate(this.minDate, this.dateFormat);
				return "";
			},

			maxVisibleDate() {
				if (this.dateUserInput.max !== null) return this.dateUserInput.max;
				if (this.maxDate || this.minDate)
					return formatDate(this.maxDate || this.minDate, this.dateFormat);
				return "";
			},

			minVisibleTime() {
				if (this.timeUserInput.min !== null) return this.timeUserInput.min;
				if (this.minDate) return formatDate(this.minDate, this.timeFormat);
				return "";
			},

			maxVisibleTime() {
				if (this.timeUserInput.max !== null) return this.timeUserInput.max;
				if (this.maxDate || this.minDate)
					return formatDate(this.maxDate || this.minDate, this.timeFormat);
				return "";
			},

			timeFormat() {
				if (this.format) {
					return extractTimeFormat(this.format);
				} else {
					return "HH:mm:ss";
				}
			},

			dateFormat() {
				if (this.format) {
					return extractDateFormat(this.format);
				} else {
					return "yyyy-MM-dd";
				}
			},

			enableMonthArrow() {
				const nextMonth = (this.leftMonth + 1) % 12;
				const yearOffset = this.leftMonth + 1 >= 12 ? 1 : 0;
				return (
					this.unlinkPanels &&
					new Date(this.leftYear + yearOffset, nextMonth) <
						new Date(this.rightYear, this.rightMonth)
				);
			},

			enableYearArrow() {
				return (
					this.unlinkPanels &&
					this.rightYear * 12 +
						this.rightMonth -
						(this.leftYear * 12 + this.leftMonth + 1) >=
						12
				);
			}
		},

		data() {
			return {
				popperClass: "",
				value: [],
				defaultValue: null,
				defaultTime: null,
				minDate: "",
				maxDate: "",
				leftDate: new Date(),
				rightDate: nextMonth(new Date()),
				rangeState: {
					endDate: null,
					selecting: false,
					row: null,
					column: null
				},
				showTime: false,
				shortcuts: "",
				visible: "",
				disabledDate: "",
				cellClassName: "",
				firstDayOfWeek: 7,
				minTimePickerVisible: false,
				maxTimePickerVisible: false,
				format: "",
				arrowControl: false,
				unlinkPanels: false,
				dateUserInput: {
					min: null,
					max: null
				},
				timeUserInput: {
					min: null,
					max: null
				}
			};
		},

		watch: {
			minDate(val) {
				this.dateUserInput.min = null;
				this.timeUserInput.min = null;
				this.$nextTick(() => {
					if (this.$refs.maxTimePicker && this.maxDate && this.maxDate < this.minDate) {
						const format = "HH:mm:ss";
						this.$refs.maxTimePicker.selectableRange = [
							[
								parseDate(formatDate(this.minDate, format), format),
								parseDate("23:59:59", format)
							]
						];
					}
				});
				if (val && this.$refs.minTimePicker) {
					this.$refs.minTimePicker.date = val;
					this.$refs.minTimePicker.value = val;
				}
			},

			maxDate(val) {
				this.dateUserInput.max = null;
				this.timeUserInput.max = null;
				if (val && this.$refs.maxTimePicker) {
					this.$refs.maxTimePicker.date = val;
					this.$refs.maxTimePicker.value = val;
				}
			},

			minTimePickerVisible(val) {
				if (val) {
					this.$nextTick(() => {
						this.$refs.minTimePicker.date = this.minDate;
						this.$refs.minTimePicker.value = this.minDate;
						this.$refs.minTimePicker.adjustSpinners();
					});
				}
			},

			maxTimePickerVisible(val) {
				if (val) {
					this.$nextTick(() => {
						this.$refs.maxTimePicker.date = this.maxDate;
						this.$refs.maxTimePicker.value = this.maxDate;
						this.$refs.maxTimePicker.adjustSpinners();
					});
				}
			},

			value(newVal) {
				if (!newVal) {
					this.minDate = null;
					this.maxDate = null;
				} else if (Array.isArray(newVal)) {
					this.minDate = isDate(newVal[0]) ? new Date(newVal[0]) : null;
					this.maxDate = isDate(newVal[1]) ? new Date(newVal[1]) : null;
					if (this.minDate) {
						this.leftDate = this.minDate;
						if (this.unlinkPanels && this.maxDate) {
							const minDateYear = this.minDate.getFullYear();
							const minDateMonth = this.minDate.getMonth();
							const maxDateYear = this.maxDate.getFullYear();
							const maxDateMonth = this.maxDate.getMonth();
							this.rightDate =
								minDateYear === maxDateYear && minDateMonth === maxDateMonth
									? nextMonth(this.maxDate)
									: this.maxDate;
						} else {
							this.rightDate = nextMonth(this.leftDate);
						}
					} else {
						this.leftDate = calcDefaultValue(this.defaultValue)[0];
						this.rightDate = nextMonth(this.leftDate);
					}
				}
			},

			defaultValue(val) {
				if (!Array.isArray(this.value)) {
					const [left, right] = calcDefaultValue(val);
					this.leftDate = left;
					this.rightDate =
						val && val[1] && this.unlinkPanels ? right : nextMonth(this.leftDate);
				}
			}
		},

		methods: {
			handleClear() {
				this.minDate = null;
				this.maxDate = null;
				this.leftDate = calcDefaultValue(this.defaultValue)[0];
				this.rightDate = nextMonth(this.leftDate);
				this.$emit("pick", null);
			},

			handleChangeRange(val) {
				this.minDate = val.minDate;
				this.maxDate = val.maxDate;
				this.rangeState = val.rangeState;
			},

			handleDateInput(value, type) {
				this.dateUserInput[type] = value;
				if (value.length !== this.dateFormat.length) return;
				const parsedValue = parseDate(value, this.dateFormat);

				if (parsedValue) {
					if (
						typeof this.disabledDate === "function" &&
						this.disabledDate(new Date(parsedValue))
					) {
						return;
					}
					if (type === "min") {
						this.minDate = modifyDate(
							this.minDate || new Date(),
							parsedValue.getFullYear(),
							parsedValue.getMonth(),
							parsedValue.getDate()
						);
						this.leftDate = new Date(parsedValue);
						if (!this.unlinkPanels) {
							this.rightDate = nextMonth(this.leftDate);
						}
					} else {
						this.maxDate = modifyDate(
							this.maxDate || new Date(),
							parsedValue.getFullYear(),
							parsedValue.getMonth(),
							parsedValue.getDate()
						);
						this.rightDate = new Date(parsedValue);
						if (!this.unlinkPanels) {
							this.leftDate = prevMonth(parsedValue);
						}
					}
				}
			},

			handleDateChange(value, type) {
				const parsedValue = parseDate(value, this.dateFormat);
				if (parsedValue) {
					if (type === "min") {
						this.minDate = modifyDate(
							this.minDate,
							parsedValue.getFullYear(),
							parsedValue.getMonth(),
							parsedValue.getDate()
						);
						if (this.minDate > this.maxDate) {
							this.maxDate = this.minDate;
						}
					} else {
						this.maxDate = modifyDate(
							this.maxDate,
							parsedValue.getFullYear(),
							parsedValue.getMonth(),
							parsedValue.getDate()
						);
						if (this.maxDate < this.minDate) {
							this.minDate = this.maxDate;
						}
					}
				}
			},

			handleTimeInput(value, type) {
				this.timeUserInput[type] = value;
				if (value.length !== this.timeFormat.length) return;
				const parsedValue = parseDate(value, this.timeFormat);

				if (parsedValue) {
					if (type === "min") {
						this.minDate = modifyTime(
							this.minDate,
							parsedValue.getHours(),
							parsedValue.getMinutes(),
							parsedValue.getSeconds()
						);
						this.$nextTick(_ => this.$refs.minTimePicker.adjustSpinners());
					} else {
						this.maxDate = modifyTime(
							this.maxDate,
							parsedValue.getHours(),
							parsedValue.getMinutes(),
							parsedValue.getSeconds()
						);
						this.$nextTick(_ => this.$refs.maxTimePicker.adjustSpinners());
					}
				}
			},

			handleTimeChange(value, type) {
				const parsedValue = parseDate(value, this.timeFormat);
				if (parsedValue) {
					if (type === "min") {
						this.minDate = modifyTime(
							this.minDate,
							parsedValue.getHours(),
							parsedValue.getMinutes(),
							parsedValue.getSeconds()
						);
						if (this.minDate > this.maxDate) {
							this.maxDate = this.minDate;
						}
						this.$refs.minTimePicker.value = this.minDate;
						this.minTimePickerVisible = false;
					} else {
						this.maxDate = modifyTime(
							this.maxDate,
							parsedValue.getHours(),
							parsedValue.getMinutes(),
							parsedValue.getSeconds()
						);
						if (this.maxDate < this.minDate) {
							this.minDate = this.maxDate;
						}
						this.$refs.maxTimePicker.value = this.minDate;
						this.maxTimePickerVisible = false;
					}
				}
			},

			handleRangePick(val, close = true) {
				const defaultTime = this.defaultTime || [];
				const minDate = modifyWithTimeString(val.minDate, defaultTime[0]);
				const maxDate = modifyWithTimeString(val.maxDate, defaultTime[1]);

				if (this.maxDate === maxDate && this.minDate === minDate) {
					return;
				}
				this.onPick && this.onPick(val);
				this.maxDate = maxDate;
				this.minDate = minDate;

				// workaround for https://github.com/ElemeFE/element/issues/7539, should remove this block when we don't have to care about Chromium 55 - 57
				setTimeout(() => {
					this.maxDate = maxDate;
					this.minDate = minDate;
				}, 10);
				if (!close || this.showTime) return;
				this.handleConfirm();
			},

			handleShortcutClick(shortcut) {
				if (shortcut.onClick) {
					shortcut.onClick(this);
				}
			},

			handleMinTimePick(value, visible, first) {
				this.minDate = this.minDate || new Date();
				if (value) {
					this.minDate = modifyTime(
						this.minDate,
						value.getHours(),
						value.getMinutes(),
						value.getSeconds()
					);
				}

				if (!first) {
					this.minTimePickerVisible = visible;
				}

				if (
					!this.maxDate ||
					(this.maxDate && this.maxDate.getTime() < this.minDate.getTime())
				) {
					this.maxDate = new Date(this.minDate);
				}
			},

			handleMinTimeClose() {
				this.minTimePickerVisible = false;
			},

			handleMaxTimePick(value, visible, first) {
				if (this.maxDate && value) {
					this.maxDate = modifyTime(
						this.maxDate,
						value.getHours(),
						value.getMinutes(),
						value.getSeconds()
					);
				}

				if (!first) {
					this.maxTimePickerVisible = visible;
				}

				if (
					this.maxDate &&
					this.minDate &&
					this.minDate.getTime() > this.maxDate.getTime()
				) {
					this.minDate = new Date(this.maxDate);
				}
			},

			handleMaxTimeClose() {
				this.maxTimePickerVisible = false;
			},

			// leftPrev*, rightNext* need to take care of `unlinkPanels`
			leftPrevYear() {
				this.leftDate = prevYear(this.leftDate);
				if (!this.unlinkPanels) {
					this.rightDate = nextMonth(this.leftDate);
				}
			},

			leftPrevMonth() {
				this.leftDate = prevMonth(this.leftDate);
				if (!this.unlinkPanels) {
					this.rightDate = nextMonth(this.leftDate);
				}
			},

			rightNextYear() {
				if (!this.unlinkPanels) {
					this.leftDate = nextYear(this.leftDate);
					this.rightDate = nextMonth(this.leftDate);
				} else {
					this.rightDate = nextYear(this.rightDate);
				}
			},

			rightNextMonth() {
				if (!this.unlinkPanels) {
					this.leftDate = nextMonth(this.leftDate);
					this.rightDate = nextMonth(this.leftDate);
				} else {
					this.rightDate = nextMonth(this.rightDate);
				}
			},

			// leftNext*, rightPrev* are called when `unlinkPanels` is true
			leftNextYear() {
				this.leftDate = nextYear(this.leftDate);
			},

			leftNextMonth() {
				this.leftDate = nextMonth(this.leftDate);
			},

			rightPrevYear() {
				this.rightDate = prevYear(this.rightDate);
			},

			rightPrevMonth() {
				this.rightDate = prevMonth(this.rightDate);
			},

			handleConfirm(visible = false) {
				if (this.isValidValue([this.minDate, this.maxDate])) {
					this.$emit("pick", [this.minDate, this.maxDate], visible);
				}
			},

			isValidValue(value) {
				return (
					Array.isArray(value) &&
					value &&
					value[0] &&
					value[1] &&
					isDate(value[0]) &&
					isDate(value[1]) &&
					value[0].getTime() <= value[1].getTime() &&
					(typeof this.disabledDate === "function"
						? !this.disabledDate(value[0]) && !this.disabledDate(value[1])
						: true)
				);
			},

			resetView() {
				// NOTE: this is a hack to reset {min, max}Date on picker open.
				// TODO: correct way of doing so is to refactor {min, max}Date to be dependent on value and internal selection state
				//       an alternative would be resetView whenever picker becomes visible, should also investigate date-panel's resetView
				if (this.minDate && this.maxDate == null) this.rangeState.selecting = false;
				this.minDate = this.value && isDate(this.value[0]) ? new Date(this.value[0]) : null;
				this.maxDate = this.value && isDate(this.value[0]) ? new Date(this.value[1]) : null;
			}
		},

		components: {
			PanelTime: () =>
				_.$importVue("/common/ui-x/components/form/xDatePicker/panel/PanelTime.vue"),
			BasicDateTable: () =>
				_.$importVue("/common/ui-x/components/form/xDatePicker/basic/BasicDateTable.vue")
		}
	});
}
</script>
